package com.kds.smarthome.security.jwt;

import com.kds.smarthome.config.SmartHomeConfig;
import com.kds.smarthome.entity.Building;
import com.kds.smarthome.service.BuildingService;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Component
public class TokenProvider {

    private static final String AUTHORITIES_KEY = "auth";
    private static final String TOKEN_BLACKLIST_PREFIX = "token:blacklist:";
    private final SecretKey key;
    private final JwtParser jwtParser;
    private final long tokenValidityInMilliseconds;
    private final long tokenValidityInMillisecondsForRememberMe;

    @Autowired
    private StringRedisTemplate redisTemplate;

    public TokenProvider(SmartHomeConfig smartHomeConfig) {
        byte[] keyBytes = smartHomeConfig.getSecurity().getJwt().getSecret().getBytes(StandardCharsets.UTF_8);
        key = Keys.hmacShaKeyFor(keyBytes);
        jwtParser = Jwts.parserBuilder().setSigningKey(key).build();
        this.tokenValidityInMilliseconds =
            1000 * smartHomeConfig.getSecurity().getJwt().getTokenValidityInSeconds();
        this.tokenValidityInMillisecondsForRememberMe =
            1000 * smartHomeConfig.getSecurity().getJwt().getTokenValidityInSecondsForRememberMe();
    }

    public String createToken(Authentication authentication, boolean rememberMe) {
        String authorities = authentication.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.joining(","));

        long now = (new Date()).getTime();
        Date validity;
        if (rememberMe) {
            validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe);
        } else {
            validity = new Date(now + this.tokenValidityInMilliseconds);
        }

        // 生成唯一的token ID (jti)，用于区分同一用户的不同登录会话
        String tokenId = UUID.randomUUID().toString();
        log.debug("Token authorities: {}, tokenId: {}", authorities, tokenId);

        return Jwts
            .builder()
            .setSubject(authentication.getName())
            .claim(AUTHORITIES_KEY, authorities)
            .setId(tokenId) // 设置JWT ID
            .signWith(key, SignatureAlgorithm.HS512)
            .setExpiration(validity)
            .compact();
    }

    /**
     * 添加令牌到黑名单
     * @param token JWT令牌
     */
    public void addToBlacklist(String token) {
        try {
            // 解析token获取过期时间和tokenId
            Claims claims = jwtParser.parseClaimsJws(token).getBody();
            Date expiration = claims.getExpiration();
            long expirationTime = expiration.getTime() - System.currentTimeMillis();

            // 获取tokenId，用作黑名单的键
            String tokenId = claims.getId();
            String username = claims.getSubject();

            // 只有当token未过期时才加入黑名单
            if (expirationTime > 0 && tokenId != null) {
                String blacklistKey = TOKEN_BLACKLIST_PREFIX + tokenId;
                redisTemplate.opsForValue().set(blacklistKey, username, expirationTime, TimeUnit.MILLISECONDS);
                log.debug("Token added to blacklist. TokenId: {}, Username: {}, Expires in {} ms",
                          tokenId, username, expirationTime);
            }
        } catch (Exception e) {
            log.error("Error adding token to blacklist", e);
        }
    }

    /**
     * 检查令牌是否在黑名单中
     * @param token JWT令牌
     * @return 如果在黑名单中返回true，否则返回false
     */
    public boolean isTokenBlacklisted(String token) {
        try {
            // 从token中提取tokenId
            Claims claims = jwtParser.parseClaimsJws(token).getBody();
            String tokenId = claims.getId();

            if (tokenId != null) {
                String blacklistKey = TOKEN_BLACKLIST_PREFIX + tokenId;
                Boolean exists = redisTemplate.hasKey(blacklistKey);
                return Boolean.TRUE.equals(exists);
            }
        } catch (Exception e) {
            // 如果token无效，视为未在黑名单中，后续验证会失败
            log.debug("Error checking token blacklist status: {}", e.getMessage());
        }
        return false;
    }

    public Authentication getAuthentication(String token) {
        Claims claims = jwtParser.parseClaimsJws(token).getBody();

        Collection<? extends GrantedAuthority> authorities = Arrays
            .stream(claims.get(AUTHORITIES_KEY).toString().split(","))
            .filter(auth -> !auth.trim().isEmpty())
            .map(SimpleGrantedAuthority::new)
            .collect(Collectors.toList());

        User principal = new User(claims.getSubject(), "", authorities);

        return new UsernamePasswordAuthenticationToken(principal, token, authorities);
    }

    public boolean validateToken(String authToken) {
        try {
            // 先检查令牌是否在黑名单中
            if (isTokenBlacklisted(authToken)) {
                log.info("Token is blacklisted: {}", authToken);
                return false;
            }

            jwtParser.parseClaimsJws(authToken);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            log.info("Invalid JWT token: {}", e.getMessage());
            return false;
        }
    }

    public String getUsernameFromToken(String token) {
        Claims claims = jwtParser.parseClaimsJws(token).getBody();
        return claims.getSubject();
    }

    /**
     * 从token和buildingCode获取当前建筑信息
     * @param token JWT token (带Bearer前缀)
     * @param buildingCode 建筑编码
     * @return 建筑对象，如果未找到返回null
     * @throws RuntimeException 当用户建筑列表未找到时抛出异常
     */

    @Autowired
    private BuildingService buildingService;

    public Building getCurrentBuilding(String token, String buildingCode) {


        return buildingService.findByCode(buildingCode);
    }

    /**
     * 定期清理过期的黑名单记录
     * 每天凌晨2点执行
     */
    @Scheduled(cron = "0 0 2 * * ?")
    public void cleanupBlacklist() {
        log.info("开始清理Token黑名单过期记录");
        try {
            String pattern = TOKEN_BLACKLIST_PREFIX + "*";
            Set<String> keys = redisTemplate.keys(pattern);

            if (keys != null && !keys.isEmpty()) {
                // 删除所有匹配的键
                long count = redisTemplate.delete(keys);
                log.info("从黑名单中清理了 {} 个过期Token记录", count);
            } else {
                log.info("没有找到需要清理的Token黑名单记录");
            }
        } catch (Exception e) {
            log.error("清理Token黑名单时发生错误", e);
        }
    }
}
